/**
 * @license Highcharts JS v4.0.4 (2014-09-02)
 * Exporting module
 *
 * (c) 2010-2014 Torstein Honsi
 *
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global Highcharts, document, window, Math, setTimeout */

(function (Highcharts) { // encapsulate

// create shortcuts
    var Chart = Highcharts.Chart,
        addEvent = Highcharts.addEvent,
        removeEvent = Highcharts.removeEvent,
        createElement = Highcharts.createElement,
        discardElement = Highcharts.discardElement,
        css = Highcharts.css,
        merge = Highcharts.merge,
        each = Highcharts.each,
        extend = Highcharts.extend,
        math = Math,
        mathMax = math.max,
        doc = document,
        win = window,
        isTouchDevice = Highcharts.isTouchDevice,
        M = 'M',
        L = 'L',
        DIV = 'div',
        HIDDEN = 'hidden',
        NONE = 'none',
        PREFIX = 'highcharts-',
        ABSOLUTE = 'absolute',
        PX = 'px',
        UNDEFINED,
        symbols = Highcharts.Renderer.prototype.symbols,
        defaultOptions = Highcharts.getOptions(),
        buttonOffset;

    // Add language
    extend(defaultOptions.lang, {
        printChart: 'Print chart',
        downloadPNG: 'Download PNG image',
        downloadJPEG: 'Download JPEG image',
        downloadPDF: 'Download PDF document',
        downloadSVG: 'Download SVG vector image',
        contextButtonTitle: 'Chart context menu'
    });

// Buttons and menus are collected in a separate config option set called 'navigation'.
// This can be extended later to add control buttons like zoom and pan right click menus.
    defaultOptions.navigation = {
        menuStyle: {
            border: '1px solid #A0A0A0',
            background: '#FFFFFF',
            padding: '5px 0'
        },
        menuItemStyle: {
            padding: '0 10px',
            background: NONE,
            color: '#303030',
            fontSize: isTouchDevice ? '14px' : '11px'
        },
        menuItemHoverStyle: {
            background: '#4572A5',
            color: '#FFFFFF'
        },

        buttonOptions: {
            symbolFill: '#E0E0E0',
            symbolSize: 14,
            symbolStroke: '#666',
            symbolStrokeWidth: 3,
            symbolX: 12.5,
            symbolY: 10.5,
            align: 'right',
            buttonSpacing: 3,
            height: 22,
            // text: null,
            theme: {
                fill: 'white', // capture hover
                stroke: 'none'
            },
            verticalAlign: 'top',
            width: 24
        }
    };


// Add the export related options
    defaultOptions.exporting = {
        //enabled: true,
        //filename: 'chart',
        type: 'image/png',
        url: 'http://export.highcharts.com/',
        //width: undefined,
        //scale: 2
        buttons: {
            contextButton: {
                menuClassName: PREFIX + 'contextmenu',
                //x: -10,
                symbol: 'menu',
                _titleKey: 'contextButtonTitle',
                menuItems: [{
                    textKey: 'printChart',
                    onclick: function () {
                        this.print();
                    }
                }, {
                    separator: true
                }, {
                    textKey: 'downloadPNG',
                    onclick: function () {
                        this.exportChart();
                    }
                }, {
                    textKey: 'downloadJPEG',
                    onclick: function () {
                        this.exportChart({
                            type: 'image/jpeg'
                        });
                    }
                }, {
                    textKey: 'downloadPDF',
                    onclick: function () {
                        this.exportChart({
                            type: 'application/pdf'
                        });
                    }
                }, {
                    textKey: 'downloadSVG',
                    onclick: function () {
                        this.exportChart({
                            type: 'image/svg+xml'
                        });
                    }
                }
                    // Enable this block to add "View SVG" to the dropdown menu
                    /*
                     ,{

                     text: 'View SVG',
                     onclick: function () {
                     var svg = this.getSVG()
                     .replace(/</g, '\n&lt;')
                     .replace(/>/g, '&gt;');

                     doc.body.innerHTML = '<pre>' + svg + '</pre>';
                     }
                     } // */
                ]
            }
        }
    };

// Add the Highcharts.post utility
    Highcharts.post = function (url, data, formAttributes) {
        var name,
            form;

        // create the form
        form = createElement('form', merge({
            method: 'post',
            action: url,
            enctype: 'multipart/form-data'
        }, formAttributes), {
            display: NONE
        }, doc.body);

        // add the data
        for (name in data) {
            createElement('input', {
                type: HIDDEN,
                name: name,
                value: data[name]
            }, null, form);
        }

        // submit
        form.submit();

        // clean up
        discardElement(form);
    };

    extend(Chart.prototype, {

        /**
         * Return an SVG representation of the chart
         *
         * @param additionalOptions {Object} Additional chart options for the generated SVG representation
         */
        getSVG: function (additionalOptions) {
            var chart = this,
                chartCopy,
                sandbox,
                svg,
                seriesOptions,
                sourceWidth,
                sourceHeight,
                cssWidth,
                cssHeight,
                options = merge(chart.options, additionalOptions); // copy the options and add extra options

            // IE compatibility hack for generating SVG content that it doesn't really understand
            if (!doc.createElementNS) {
                /*jslint unparam: true*/
                /* allow unused parameter ns in function below */
                doc.createElementNS = function (ns, tagName) {
                    return doc.createElement(tagName);
                };
                /*jslint unparam: false*/
            }

            // create a sandbox where a new chart will be generated
            sandbox = createElement(DIV, null, {
                position: ABSOLUTE,
                top: '-9999em',
                width: chart.chartWidth + PX,
                height: chart.chartHeight + PX
            }, doc.body);

            // get the source size
            cssWidth = chart.renderTo.style.width;
            cssHeight = chart.renderTo.style.height;
            sourceWidth = options.exporting.sourceWidth ||
                options.chart.width ||
                (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||
                600;
            sourceHeight = options.exporting.sourceHeight ||
                options.chart.height ||
                (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||
                400;

            // override some options
            extend(options.chart, {
                animation: false,
                renderTo: sandbox,
                forExport: true,
                width: sourceWidth,
                height: sourceHeight
            });
            options.exporting.enabled = false; // hide buttons in print

            // prepare for replicating the chart
            options.series = [];
            each(chart.series, function (serie) {
                seriesOptions = merge(serie.options, {
                    animation: false, // turn off animation
                    enableMouseTracking: false,
                    showCheckbox: false,
                    visible: serie.visible
                });

                if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
                    options.series.push(seriesOptions);
                }
            });

            // generate the chart copy
            chartCopy = new Highcharts.Chart(options, chart.callback);

            // reflect axis extremes in the export
            each(['xAxis', 'yAxis'], function (axisType) {
                each(chart[axisType], function (axis, i) {
                    var axisCopy = chartCopy[axisType][i],
                        extremes = axis.getExtremes(),
                        userMin = extremes.userMin,
                        userMax = extremes.userMax;

                    if (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {
                        axisCopy.setExtremes(userMin, userMax, true, false);
                    }
                });
            });

            // get the SVG from the container's innerHTML
            svg = chartCopy.container.innerHTML;

            // free up memory
            options = null;
            chartCopy.destroy();
            discardElement(sandbox);

            // sanitize
            svg = svg
                .replace(/zIndex="[^"]+"/g, '')
                .replace(/isShadow="[^"]+"/g, '')
                .replace(/symbolName="[^"]+"/g, '')
                .replace(/jQuery[0-9]+="[^"]+"/g, '')
                .replace(/url\([^#]+#/g, 'url(#')
                .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
                .replace(/ href=/g, ' xlink:href=')
                .replace(/\n/, ' ')
                // Any HTML added to the container after the SVG (#894)
                .replace(/<\/svg>.*?$/, '</svg>')
                // Batik doesn't support rgba fills and strokes (#3095)
                .replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g, '$1="rgb($2)" $1-opacity="$3"')
                /* This fails in IE < 8
                 .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
                 return s2 +'.'+ s3[0];
                 })*/

                // Replace HTML entities, issue #347
                .replace(/&nbsp;/g, '\u00A0') // no-break space
                .replace(/&shy;/g, '\u00AD') // soft hyphen

                // IE specific
                .replace(/<IMG /g, '<image ')
                .replace(/height=([^" ]+)/g, 'height="$1"')
                .replace(/width=([^" ]+)/g, 'width="$1"')
                .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
                .replace(/id=([^" >]+)/g, 'id="$1"')
                .replace(/class=([^" >]+)/g, 'class="$1"')
                .replace(/ transform /g, ' ')
                .replace(/:(path|rect)/g, '$1')
                .replace(/style="([^"]+)"/g, function (s) {
                    return s.toLowerCase();
                });

            // IE9 beta bugs with innerHTML. Test again with final IE9.
            svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
                .replace(/&quot;/g, "'");

            return svg;
        },

        /**
         * Submit the SVG representation of the chart to the server
         * @param {Object} options Exporting options. Possible members are url, type, width and formAttributes.
         * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
         */
        exportChart: function (options, chartOptions) {
            options = options || {};

            var chart = this,
                chartExportingOptions = chart.options.exporting,
                svg = chart.getSVG(merge(
                    {chart: {borderRadius: 0}},
                    chartExportingOptions.chartOptions,
                    chartOptions,
                    {
                        exporting: {
                            sourceWidth: options.sourceWidth || chartExportingOptions.sourceWidth,
                            sourceHeight: options.sourceHeight || chartExportingOptions.sourceHeight
                        }
                    }
                ));

            // merge the options
            options = merge(chart.options.exporting, options);

            // do the post
            Highcharts.post(options.url, {
                filename: options.filename || 'chart',
                type: options.type,
                width: options.width || 0, // IE8 fails to post undefined correctly, so use 0
                scale: options.scale || 2,
                svg: svg
            }, options.formAttributes);

        },

        /**
         * Print the chart
         */
        print: function () {

            var chart = this,
                container = chart.container,
                origDisplay = [],
                origParent = container.parentNode,
                body = doc.body,
                childNodes = body.childNodes;

            if (chart.isPrinting) { // block the button while in printing mode
                return;
            }

            chart.isPrinting = true;

            // hide all body content
            each(childNodes, function (node, i) {
                if (node.nodeType === 1) {
                    origDisplay[i] = node.style.display;
                    node.style.display = NONE;
                }
            });

            // pull out the chart
            body.appendChild(container);

            // print
            win.focus(); // #1510
            win.print();

            // allow the browser to prepare before reverting
            setTimeout(function () {

                // put the chart back in
                origParent.appendChild(container);

                // restore all body content
                each(childNodes, function (node, i) {
                    if (node.nodeType === 1) {
                        node.style.display = origDisplay[i];
                    }
                });

                chart.isPrinting = false;

            }, 1000);

        },

        /**
         * Display a popup menu for choosing the export type
         *
         * @param {String} className An identifier for the menu
         * @param {Array} items A collection with text and onclicks for the items
         * @param {Number} x The x position of the opener button
         * @param {Number} y The y position of the opener button
         * @param {Number} width The width of the opener button
         * @param {Number} height The height of the opener button
         */
        contextMenu: function (className, items, x, y, width, height, button) {
            var chart = this,
                navOptions = chart.options.navigation,
                menuItemStyle = navOptions.menuItemStyle,
                chartWidth = chart.chartWidth,
                chartHeight = chart.chartHeight,
                cacheName = 'cache-' + className,
                menu = chart[cacheName],
                menuPadding = mathMax(width, height), // for mouse leave detection
                boxShadow = '3px 3px 10px #888',
                innerMenu,
                hide,
                hideTimer,
                menuStyle,
                docMouseUpHandler = function (e) {
                    if (!chart.pointer.inClass(e.target, className)) {
                        hide();
                    }
                };

            // create the menu only the first time
            if (!menu) {

                // create a HTML element above the SVG
                chart[cacheName] = menu = createElement(DIV, {
                    className: className
                }, {
                    position: ABSOLUTE,
                    zIndex: 1000,
                    padding: menuPadding + PX
                }, chart.container);

                innerMenu = createElement(DIV, null,
                    extend({
                        MozBoxShadow: boxShadow,
                        WebkitBoxShadow: boxShadow,
                        boxShadow: boxShadow
                    }, navOptions.menuStyle), menu);

                // hide on mouse out
                hide = function () {
                    css(menu, {display: NONE});
                    if (button) {
                        button.setState(0);
                    }
                    chart.openMenu = false;
                };

                // Hide the menu some time after mouse leave (#1357)
                addEvent(menu, 'mouseleave', function () {
                    hideTimer = setTimeout(hide, 500);
                });
                addEvent(menu, 'mouseenter', function () {
                    clearTimeout(hideTimer);
                });


                // Hide it on clicking or touching outside the menu (#2258, #2335, #2407)
                addEvent(document, 'mouseup', docMouseUpHandler);
                addEvent(chart, 'destroy', function () {
                    removeEvent(document, 'mouseup', docMouseUpHandler);
                });


                // create the items
                each(items, function (item) {
                    if (item) {
                        var element = item.separator ?
                            createElement('hr', null, null, innerMenu) :
                            createElement(DIV, {
                                onmouseover: function () {
                                    css(this, navOptions.menuItemHoverStyle);
                                },
                                onmouseout: function () {
                                    css(this, menuItemStyle);
                                },
                                onclick: function () {
                                    hide();
                                    item.onclick.apply(chart, arguments);
                                },
                                innerHTML: item.text || chart.options.lang[item.textKey]
                            }, extend({
                                cursor: 'pointer'
                            }, menuItemStyle), innerMenu);


                        // Keep references to menu divs to be able to destroy them
                        chart.exportDivElements.push(element);
                    }
                });

                // Keep references to menu and innerMenu div to be able to destroy them
                chart.exportDivElements.push(innerMenu, menu);

                chart.exportMenuWidth = menu.offsetWidth;
                chart.exportMenuHeight = menu.offsetHeight;
            }

            menuStyle = {display: 'block'};

            // if outside right, right align it
            if (x + chart.exportMenuWidth > chartWidth) {
                menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
            } else {
                menuStyle.left = (x - menuPadding) + PX;
            }
            // if outside bottom, bottom align it
            if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {
                menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
            } else {
                menuStyle.top = (y + height - menuPadding) + PX;
            }

            css(menu, menuStyle);
            chart.openMenu = true;
        },

        /**
         * Add the export button to the chart
         */
        addButton: function (options) {
            var chart = this,
                renderer = chart.renderer,
                btnOptions = merge(chart.options.navigation.buttonOptions, options),
                onclick = btnOptions.onclick,
                menuItems = btnOptions.menuItems,
                symbol,
                button,
                symbolAttr = {
                    stroke: btnOptions.symbolStroke,
                    fill: btnOptions.symbolFill
                },
                symbolSize = btnOptions.symbolSize || 12;
            if (!chart.btnCount) {
                chart.btnCount = 0;
            }

            // Keeps references to the button elements
            if (!chart.exportDivElements) {
                chart.exportDivElements = [];
                chart.exportSVGElements = [];
            }

            if (btnOptions.enabled === false) {
                return;
            }


            var attr = btnOptions.theme,
                states = attr.states,
                hover = states && states.hover,
                select = states && states.select,
                callback;

            delete attr.states;

            if (onclick) {
                callback = function () {
                    onclick.apply(chart, arguments);
                };

            } else if (menuItems) {
                callback = function () {
                    chart.contextMenu(
                        button.menuClassName,
                        menuItems,
                        button.translateX,
                        button.translateY,
                        button.width,
                        button.height,
                        button
                    );
                    button.setState(2);
                };
            }


            if (btnOptions.text && btnOptions.symbol) {
                attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);

            } else if (!btnOptions.text) {
                extend(attr, {
                    width: btnOptions.width,
                    height: btnOptions.height,
                    padding: 0
                });
            }

            button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
                .attr({
                    title: chart.options.lang[btnOptions._titleKey],
                    'stroke-linecap': 'round'
                });
            button.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;

            if (btnOptions.symbol) {
                symbol = renderer.symbol(
                    btnOptions.symbol,
                    btnOptions.symbolX - (symbolSize / 2),
                    btnOptions.symbolY - (symbolSize / 2),
                    symbolSize,
                    symbolSize
                )
                    .attr(extend(symbolAttr, {
                        'stroke-width': btnOptions.symbolStrokeWidth || 1,
                        zIndex: 1
                    })).add(button);
            }

            button.add()
                .align(extend(btnOptions, {
                    width: button.width,
                    x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654
                }), true, 'spacingBox');

            buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);

            chart.exportSVGElements.push(button, symbol);

        },

        /**
         * Destroy the buttons.
         */
        destroyExport: function (e) {
            var chart = e.target,
                i,
                elem;

            // Destroy the extra buttons added
            for (i = 0; i < chart.exportSVGElements.length; i++) {
                elem = chart.exportSVGElements[i];

                // Destroy and null the svg/vml elements
                if (elem) { // #1822
                    elem.onclick = elem.ontouchstart = null;
                    chart.exportSVGElements[i] = elem.destroy();
                }
            }

            // Destroy the divs for the menu
            for (i = 0; i < chart.exportDivElements.length; i++) {
                elem = chart.exportDivElements[i];

                // Remove the event handler
                removeEvent(elem, 'mouseleave');

                // Remove inline events
                chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;

                // Destroy the div by moving to garbage bin
                discardElement(elem);
            }
        }
    });


    symbols.menu = function (x, y, width, height) {
        var arr = [
            M, x, y + 2.5,
            L, x + width, y + 2.5,
            M, x, y + height / 2 + 0.5,
            L, x + width, y + height / 2 + 0.5,
            M, x, y + height - 1.5,
            L, x + width, y + height - 1.5
        ];
        return arr;
    };

// Add the buttons on chart load
    Chart.prototype.callbacks.push(function (chart) {
        var n,
            exportingOptions = chart.options.exporting,
            buttons = exportingOptions.buttons;

        buttonOffset = 0;

        if (exportingOptions.enabled !== false) {

            for (n in buttons) {
                chart.addButton(buttons[n]);
            }

            // Destroy the export elements at chart destroy
            addEvent(chart, 'destroy', chart.destroyExport);
        }

    });


}(Highcharts));
